07. Coding: Building a Game Class
Representing the Game State in Code
Before we can code the minimax algorithm, we need a way to keep track of the game state while the program runs. We'll start with a simple class for the mini-Isolation game from the lecture examples. (Using a simple example will make it easier to reason about expected correct behavior while debugging & testing the code.)
Game Class Requirements
The game state object needs to enforce all of the rules of the game, and represent all of the information describing a single configuration of the game at a specific point in time. There isn't only one "Right Way" to do this, so we'll have to make some additional assumptions that constrain the solution space -- but you shouldn't worry if your choices don't exactly match the examples shown.
The basic Board class is going to be very simple. In this quiz we're only going to write the class constructor method __init__()
, then we'll add functionality to the class in later quizzes. Your Isolation GameState
class needs properties that can :
- keep track of which cells are open and closed
- identify which player has initiative (whose turn it is to move)
- track the current position each player on the board
Note: the constructor should make sure that the bottom corner cell is blocked when a new instance is created!
We will assume that board origin is in the top left corner and set the x axis to move from left to right and the y axis to move from top to bottom (the usual semantics for 2d array indexing). Each cell on the board can be described by an ordered pair (x, y)
. Thus (0, 0) is the top-left corner cell; (2, 1) is the bottom right corner (the blocked cell).
If you get stuck, flip over to the solution to see one possible implementation.
Start Quiz:
# TODO: implement the __init__ class below by adding properties
# that meet the three requirements specified
class GameState:
def __init__(self):
"""The GameState class constructor performs required
initializations when an instance is created. The class
should:
1) Keep track of which cells are open/closed
2) Identify which player has initiative
3) Record the current location of each player
Parameters
----------
self:
instance methods automatically take "self" as an
argument in python
Returns
-------
None
"""
# You can define attributes like this:
# self.value = 73 # an arbitrary number
# reassign it to a string (variable type is dynamic in Python)
# self.value = "some string"
# self.foo = [] # create an empty list
raise NotImplementedError
if __name__ == "__main__":
# This code is only executed if "gameagent.py" is the run
# as a script (i.e., it is not run if "gameagent.py" is
# imported as a module)
emptyState = GameState() # create an instance of the object
# Feel free to modify this code for testing, however the "submit" button
# will NOT recognize any of these changes
from gamestate import *
print("Creating empty game board...")
emptyState = GameState()
print("Everything looks good!")
# This is just one possible solution, there are many
# other options that will work just as well or better
xlim, ylim = 3, 2 # board dimension constants
class GameState:
"""
Attributes
----------
_board: list(list)
Represent the board with a 2d array _board[x][y]
where open spaces are 0 and closed spaces are 1
and a coordinate system where [0][0] is the top-
left corner, and x increases to the right while
y increases going down (this is an arbitrary
convention choice -- there are many other options
that are just as good)
_parity: bool
Keep track of active player initiative (which
player has control to move) where 0 indicates that
player one has initiative and 1 indicates player two
_player_locations: list(tuple)
Keep track of the current location of each player
on the board where position is encoded by the
board indices of their last move, e.g., [(0, 0), (1, 0)]
means player one is at (0, 0) and player two is at (1, 0)
"""
def __init__(self):
# single-underscore prefix on attribute names means
# that the attribute is "private" (Python doesn't truly
# support public/private members, so this is only a
# convention)
self._board = [[0] * ylim for _ in range(xlim)]
self._board[-1][-1] = 1 # block lower-right corner
self._parity = 0
self._player_locations = [None, None]
INSTRUCTOR NOTE:
The test case for this class will accept any code that removes the error raised by the constructor method…which should make sense because the class doesn't do anything yet. (Don't worry, we'll get there soon enough.)
NOTE: In general, the test cases used by classroom quizzes or the Project Assistant to evaluate your code will not be available to you, so you'll need to get in the habit of writing your own test cases to check the correctness of your code. The requirements that specify functionality in the task description or docstrings are usually good candidates for writing test cases. We'll go through some examples in the classroom of writing test cases, but for now it's a good idea to spend a little time thinking about how you would test your class to make sure it meets all requirements.